/**
 * 
 */
package com.cooksys.film_rental.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.cooksys.film_rental.dao.CustomerDao;
import com.cooksys.film_rental.model.Address;
import com.cooksys.film_rental.model.Customer;
import com.cooksys.film_rental.model.Film;
import com.cooksys.film_rental.model.Payment;
import com.cooksys.film_rental.model.Rental;
import com.cooksys.film_rental.model.Store;
import com.cooksys.film_rental.model.User;

@Repository("CustomerDao")
@Transactional
public class CustomerDaoImpl implements CustomerDao {

	@Autowired(required = true)
	private SessionFactory sessionFactory;

	/**
	 * 
	 * @return the current session from the internal {@link SessionFactory}
	 */
	public Session currentSession() {
		return sessionFactory.getCurrentSession();
	}

	@Override
	public Customer save(Customer customer) {
		Session session = currentSession();
		Serializable id = session.save(customer);
		return (Customer) session.get(Customer.class, id);

	}

	@Override
	public void update(Customer customer) {
		Session session = currentSession();
		session.update(customer);

	}

	@Override
	public void delete(Customer customer) {
		Session session = currentSession();
		session.delete(customer);

	}

	@Override
	public Customer getCustomerByCustomerId(Short customerId) {
		Session session = currentSession();

		// returns a record from the CUSTOMER table as a Customer object based
		// on the given customerId, the table's primary key
		String hql = "from Customer where customerId = :customerId";
		Query query = session.createQuery(hql);
		query.setShort("customerId", customerId);

		return (Customer) query.list().get(0);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Customer> searchCustomersByAddress(Address address) {
		Session session = currentSession();

		// selects a list of customer objects associated with the given address
		String hql = "from Customer c where c.address = :address";
		Query query = session.createQuery(hql);
		query.setEntity("address", address);

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Customer> searchCustomersByStore(Store store) {
		Session session = currentSession();

		// selects a list of customer objects associated with the given store
		String hql = "from Customer c where c.store = :store";
		Query query = session.createQuery(hql);
		query.setEntity("store", store);

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Customer> searchCustomersByPartialName(String partialName) {
		Session session = currentSession();

		// split the search string into search terms
		String[] nameFragments = partialName.split("\\s");

		// 'select' statement of the hql query
		String hql = "from Customer c where ";

		// generating the where clause
		for (int i = 0; i < nameFragments.length; i++) {

			// for each search term, add a segment of the where clause that
			// checks both the first_name and last_name columns of the CUSTOMER
			// table for matches
			hql += "c.firstName like :nameFragment" + i
					+ " or c.lastName like :nameFragment" + i;

			if (i < nameFragments.length - 1) {

				// concatenate these where clause segments with an 'or' operator
				hql += " or ";

			}

		}

		Query query = session.createQuery(hql);

		for (int i = 0; i < nameFragments.length; i++) {

			// parameterize the query with the search terms
			query.setString("nameFragment" + i, "%" + nameFragments[i] + "%");

		}

		return query.list();

	}

	@Override
	public Customer getCustomerByUser(User user) {
		Session session = currentSession();

		// selects a customer object associated with the given user
		String hql = "select u.customer from User u where u = :user";
		Query query = session.createQuery(hql);
		query.setEntity("user", user);

		return (Customer) query.list().get(0);
	}

	@Override
	public Customer getCustomerByRental(Rental rental) {
		Session session = currentSession();

		// selects a customer object associated with the given rental
		String hql = "select r.customer from Rental r where r = :rental";
		Query query = session.createQuery(hql);
		query.setEntity("rental", rental);

		return (Customer) query.list().get(0);
	}

	@Override
	public Customer getCustomerByPayment(Payment payment) {
		Session session = currentSession();

		// selects a customer object associated with the given user
		String hql = "select p.customer from Payment p where p = :payment";
		Query query = session.createQuery(hql);
		query.setEntity("payment", payment);

		return (Customer) query.list().get(0);
	}

	@Override
	public void saveOrUpdate(Customer customer) {
		Session session = currentSession();

		session.saveOrUpdate(customer);

	}

	@Override
	public Customer getCustomerByInfoAndAddressAndStore(String firstName,
			String lastName, String email, Address address, Store store) {
		Session session = currentSession();

		String hql = "from Customer c where c.firstName = :firstName and c.lastName = :lastName and c.email = :email and c.address = :address and c.store = :store";
		Query query = session.createQuery(hql);

		query.setString("firstName", firstName);
		query.setString("lastName", lastName);
		query.setString("email", email);
		query.setEntity("address", address);
		query.setEntity("store", store);

		return (Customer) query.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Film> getFilmsInCart(Customer customer) {
		Session session = currentSession();

		String hql = "from Film f where "
				+ " not exists "
				+ "(select p.paymentId from Payment p where p.rental.customer.customerId = :customerId and p.rental.returnDate = null and p.rental.inventory.film.filmId = f.filmId)"
				+ " and "
				+ " exists "
				+ "(select r.rentalId from Rental r where r.customer.customerId = :customerId and r.returnDate = null and r.inventory.film.filmId = f.filmId)";
		Query query = session.createQuery(hql);
		query.setShort("customerId", customer.getCustomerId());

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Film> getRentedFilms(Customer customer) {
		Session session = currentSession();

		String hql = "from Film f where "
				+ " exists "
				+ "(select p.paymentId from Payment p where p.rental.customer.customerId = :customerId and p.rental.returnDate = null and p.rental.inventory.film.filmId = f.filmId)"
				+ " and "
				+ " exists "
				+ "(select r.rentalId from Rental r where r.customer.customerId = :customerId and r.returnDate = null and r.inventory.film.filmId = f.filmId)";
		Query query = session.createQuery(hql);
		query.setShort("customerId", customer.getCustomerId());

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Rental> getRentalsInCart(Customer customer) {
		if (customer == null) {
			return new ArrayList<Rental>();
		}
		Session session = currentSession();

		String hql = "from Rental r where "
				+ " r.customer.customerId = :customerId and r.returnDate = null "
				+ " and not exists "
				+ "(select p.paymentId from Payment p where p.rental.rentalId = r.rentalId)";
		Query query = session.createQuery(hql);
		query.setShort("customerId", customer.getCustomerId());

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Rental> getPaidRentals(Customer customer) {
		if (customer == null) {
			return new ArrayList<Rental>();
		}
		Session session = currentSession();

		String hql = "from Rental r where "
				+ " r.customer.customerId = :customerId and r.returnDate = null "
				+ " and exists "
				+ "(select p.paymentId from Payment p where p.rental.rentalId = r.rentalId)";
		Query query = session.createQuery(hql);
		query.setShort("customerId", customer.getCustomerId());

		return query.list();
	}
}
